#include "Mesh.h"
#include "ModelAnimation.h"
#include <glad/glad.h>
#include <iostream>
#include <glm/gtc/matrix_transform.hpp>
#include <utility>
#include "File.h"

using namespace std;
using namespace glm;

MeshAsset::MeshAsset(const char *name, const vector<Vertex> &vertices, const vector<uint32_t> &indices, const Texture &texture, std::unordered_map<int, std::string> boneIdtoNames)
{
    this->vertices = vertices;
    this->indices = indices;
    this->texture = texture;
    this->boneIdtoNames = boneIdtoNames;
    this->name = std::string(name);
}

MeshAsset::MeshAsset(const std::string &path)
{
    FileReader reader(path);

    int vertexCount = reader.ReadInt32();
    int indexCount = reader.ReadInt32();
    int boneCount = reader.ReadInt32();

    for (int i = 0; i < boneCount; i++)
    {
        int boneId = reader.ReadInt32();
        string boneName = reader.ReadUntil('?');
        boneIdtoNames[boneId] = boneName;
    }

    vertices.resize(vertexCount);
    indices.resize(indexCount);

    // vertices
    for (auto &vertex : vertices)
    {
        vertex.Position = reader.ReadVec3();
        vertex.TexCoords = reader.ReadVec2();
        vertex.Normal = reader.ReadVec3();
        vertex.RenderMask = reader.ReadUInt32();

        for (int i = 0; i < MAX_BONE_LENGTH; ++i)
        {
            vertex.BoneId[i] = reader.ReadInt32();
        }
        for (int i = 0; i < MAX_BONE_LENGTH; ++i)
        {
            vertex.BoneWeight[i] = reader.ReadFloat32();
        }
    }

    // indices
    for (auto &index : indices)
    {
        index = reader.ReadInt32();
    }

    reader.Close();
}

void MeshAsset::ExportAsset(const std::string &path)
{
    FileWriter writer(path);
    writer.Write("Mesh_Asset");
    writer.WriteInt32(vertices.size());
    writer.WriteInt32(indices.size());
    writer.WriteInt32(boneIdtoNames.size());

    // write bone list
    for (auto &bone : boneIdtoNames)
    {
        writer.WriteInt32(bone.first);
        writer.Write(bone.second);
        writer.Write('?');
    }

    // write vertices
    for (auto &vertex : vertices)
    {
        writer.WriteVec3(vertex.Position);
        writer.WriteVec3(vertex.Normal);
        writer.WriteVec2(vertex.TexCoords);
        writer.WriteUInt32(vertex.RenderMask);

        for (int i = 0; i < MAX_BONE_LENGTH; ++i)
        {
            writer.WriteInt32(vertex.BoneId[i]);
        }
        for (int i = 0; i < MAX_BONE_LENGTH; ++i)
        {
            writer.WriteFloat32(vertex.BoneWeight[i]);
        }
    }

    // indices
    for (auto &index : indices)
    {
        writer.WriteInt32(index);
    }

    writer.Close();
}

std::shared_ptr<OrderedMesh> MeshAsset::OrderBindedMesh(std::shared_ptr<SkeletonAsset> skeletonAsset)
{
    std::shared_ptr<OrderedMesh> meshBinded = std::make_shared<OrderedMesh>(vertices, indices, texture);
    meshBinded->skeletonBinded = true;

    if (name.find("wb-") != string::npos)
    {
        meshBinded->hasBodyPart = true;
    }
    else
    {
        meshBinded->hasBodyPart = false;
    }

    // std::cout << (skeletonAsset->SkeletonType.c_str()) << endl;

    meshBinded->skeletonAsset = skeletonAsset;

    // 遍历所有顶点，查找骨骼id对应的名称，在骨架中找到对应的骨骼，将id更新为骨架的骨骼id
    for (int i = 0; i < meshBinded->vertices.size(); i++)
    {
        for (int j = 0; j < MAX_BONE_LENGTH; j++)
        {

            auto it = boneIdtoNames.find(meshBinded->vertices[i].BoneId[j]);
            if (it != boneIdtoNames.end())
            {
                if (skeletonAsset->skinBoneNameIndex.find(it->second) != skeletonAsset->skinBoneNameIndex.end())
                {
                    meshBinded->vertices[i].BoneId[j] = skeletonAsset->skinBoneNameIndex[it->second];
                }
                else
                {
                    // throw std::runtime_error("OrderedMesh:" + name + " skeleton has no such skin bone " + it->second + " id: " + meshBinded->vertices[i].BoneId[j]);
                    if (meshBinded->vertices[i].BoneWeight[j] == 0.0f)
                        meshBinded->vertices[i].BoneId[j] = 0;
                    else
                        std::cout << ("OrderedMesh:" + name + " skeleton has no such skin bone " + it->second + " id: ") << meshBinded->vertices[i].BoneId[j] << std::endl;
                }
            }
            else
            {
                throw std::runtime_error("OrderedMesh: boneIdtoNames not found, invalid bone id");
            }
        }
    }

    meshBinded->SetupMesh();
    skeletonAsset->orderedMeshes.insert(std::make_pair(name, meshBinded));
    return meshBinded;
}

void OrderedMesh::SetupMesh()
{
    if (initialized)
    {
        return;
    }

    // meshDrawIds.resize(maxInstanceCount);
    // meshDrawMask.resize(maxInstanceCount);
    instanceDatas.resize(maxInstanceCount);
    instanceDatas.clear();
    // meshDrawIds.clear();
    // meshDrawMask.clear();
    // for (int i = 0; i < maxInstanceCount; i++)
    // {
    //     meshDrawIds[i] = (uint32_t)i;
    // }

    // uint32_t datatt[maxInstanceCount];
    // for (int i = 0; i < maxInstanceCount; i++)
    // {
    //     datatt[i] = (uint32_t)i;
    // }



    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, int64_t(vertices.size() * sizeof(Vertex)), vertices.data(), GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, int64_t(indices.size() * sizeof(uint32_t)), indices.data(), GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, Position));
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, Normal));
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, TexCoords));

    glVertexAttribIPointer(4, 1, GL_UNSIGNED_INT, sizeof(Vertex), (void *)(offsetof(Vertex, RenderMask)));
    // for (int i = 0; i < MAX_BONE_LENGTH; ++i)
    // {
    //     glVertexAttribIPointer(5 + i, 1, GL_UNSIGNED_INT, sizeof(Vertex), (void *)(offsetof(Vertex, BoneId) + sizeof(Vertex::BoneId[0]) * i));
    //     glVertexAttribPointer(5 + MAX_BONE_LENGTH + i, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(offsetof(Vertex, BoneWeight) + sizeof(Vertex::BoneWeight[0]) * i));
    // }
    glVertexAttribIPointer(5, MAX_BONE_LENGTH, GL_INT, sizeof(Vertex), (void *)(offsetof(Vertex, BoneId)));
    glVertexAttribPointer(6, MAX_BONE_LENGTH, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(offsetof(Vertex, BoneWeight)));
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);

    glEnableVertexAttribArray(4);
    glEnableVertexAttribArray(5);
    glEnableVertexAttribArray(6);
    // for (int i = 0; i < MAX_BONE_LENGTH; ++i)
    // {
    //     glEnableVertexAttribArray(5 + i);
    //     glEnableVertexAttribArray(5 + MAX_BONE_LENGTH + i);
    // }
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glGenBuffers(1, &instanceVBO);
    glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(InstanceData) * maxInstanceCount, NULL, GL_DYNAMIC_DRAW);

    //注意！！！ 这里必须是IPointer，才能传递int型的数据，IPointer没有规格化参数
    glVertexAttribIPointer(7, 1, GL_UNSIGNED_INT, sizeof(InstanceData), (void *)(offsetof(InstanceData, DrawId)));
    //注意！！！ 这里必须是IPointer，才能传递int型的数据，IPointer没有规格化参数
    glVertexAttribIPointer(8, 1, GL_UNSIGNED_INT, sizeof(InstanceData), (void *)(offsetof(InstanceData, DrawMask)));

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glEnableVertexAttribArray(7);
    glEnableVertexAttribArray(8);
    glVertexAttribDivisor(7, 1);
    glVertexAttribDivisor(8, 1);

    glBindVertexArray(0);

    initialized = true;
}

std::shared_ptr<MeshInstance> OrderedMesh::CreateBindedInstance(std::shared_ptr<Skeleton> skeleton)
{
    if (!initialized)
    {
        throw std::runtime_error("OrderedMesh:CreateBindedInstance this mesh is not initialized");
    }

    if (skeletonBinded == false)
    {
        throw std::runtime_error("OrderedMesh: this mesh can not bind to skeleton, use CreateInstance instead");
    }
    std::shared_ptr<OrderedMesh> me(this);
    std::shared_ptr<MeshInstance> meshInstance = std::make_shared<MeshInstance>(me);
    meshInstance->skeleton = skeleton;
    skeleton->BindMesh(meshInstance);
    return meshInstance;
}

std::shared_ptr<MeshInstance> OrderedMesh::CreateInstance()
{
    if (!initialized)
    {
        throw std::runtime_error("OrderedMesh:CreateInstance this mesh is not initialized");
    }

    if (skeletonBinded == true)
    {
        throw std::runtime_error("OrderedMesh: this mesh is binded to skeleton, use CreateBindedInstance instead");
    }
    std::shared_ptr<OrderedMesh> me(this);
    std::shared_ptr<MeshInstance> meshInstance = std::make_shared<MeshInstance>(me);
    return meshInstance;
}

void OrderedMesh::DrawInstance(const gl::IShader &shader)
{
    if (!initialized)
    {
        throw std::runtime_error("OrderedMesh:DrawInstance: this mesh is not initialized");
    }

    if (instanceCount == 0)
    {
        // std::cout<<"DrawMesh: "<<"instanceCount is 0"<<std::endl;
        return;
    }
    // std::cout << "DrawMesh"  << "instanceCount is " << instanceCount << std::endl;

    if (instanceCount > maxInstanceCount)
    {
        std::string error = "OrderedMesh: instanceCount is too large, max is" + maxInstanceCount;
        throw std::runtime_error(error.c_str());
    }

    // std::cout << "DrawMesh" << "skeletonBinded is " << skeletonBinded << std::endl;

    shader.setBool("skeletonBinded", skeletonBinded);

    glActiveTexture(GL_TEXTURE0);
    shader.setInt("texture0", 0);
    glBindTexture(GL_TEXTURE_2D, texture.TexId);
    shader.setBool("hasBodyPart", false);

    if (hasBodyPart){
        glActiveTexture(GL_TEXTURE1);
        shader.setInt("texture1", 1);
        glBindTexture(GL_TEXTURE_2D, textureBody.TexId);
        shader.setBool("hasBodyPart", true);
    }
    glActiveTexture(GL_TEXTURE0);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    // void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    // // 复制数据到内存
    // memcpy(ptr, meshDrawIds.data(), sizeof(meshDrawIds.data()));
    // // 记得告诉OpenGL我们不再需要这个指针了
    // glUnmapBuffer(GL_ARRAY_BUFFER);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(InstanceData) * instanceCount, instanceDatas.data());
    //注意！！！ 这里必须是IPointer，才能传递int型的数据，IPointer没有规格化参数
    glVertexAttribIPointer(7, 1, GL_UNSIGNED_INT, sizeof(InstanceData), (void *)(offsetof(InstanceData, DrawId)));
    //注意！！！ 这里必须是IPointer，才能传递int型的数据，IPointer没有规格化参数
    glVertexAttribIPointer(8, 1, GL_UNSIGNED_INT, sizeof(InstanceData), (void *)(offsetof(InstanceData, DrawMask)));
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glVertexAttribDivisor(7, 1);
    glVertexAttribDivisor(8, 1);

    glDrawElementsInstanced(GL_TRIANGLES, int32_t(indices.size()), GL_UNSIGNED_INT, nullptr, instanceCount);
    glBindVertexArray(0);
    glActiveTexture(GL_TEXTURE0);
}

void OrderedMesh::ExportToFile(const char *fileName)
{
    if (!initialized)
    {
        throw std::runtime_error("OrderedMesh:ExportToFile: this mesh is not initialized");
    }

    std::cout << "Exporting: " << fileName << std::endl;

    FileWriter writer(fileName);

    writer.Write("Ora_Mesh");

    if (skeletonBinded)
    {
        if (skeletonAsset == nullptr)
        {
            throw std::runtime_error("OrderedMesh:ExportToFile: skeletonAsset is nullptr");
        }

        writer.Write(skeletonAsset->SkeletonType + '?');

        writer.WriteInt32(skeletonAsset->skinBoneNameIndex.size());
        for (auto &skinBoneNameIndex : skeletonAsset->skinBoneNameIndex)
        {
            writer.WriteInt32(skinBoneNameIndex.second);
            writer.Write(skinBoneNameIndex.first + '?');
        }
    }
    else
        writer.Write("null_skeleton?");



    writer.WriteInt32(vertices.size());
    writer.WriteInt32(indices.size());
    writer.WriteInt32(MAX_BONE_LENGTH); // skin bone count

    // vertices
    for (auto &vertex : vertices)
    {
        writer.WriteVec3(vertex.Position);
        writer.WriteVec2(vertex.TexCoords);
        writer.WriteVec3(vertex.Normal);
        writer.WriteUInt32(vertex.RenderMask);

        for (int i = 0; i < MAX_BONE_LENGTH; ++i)
        {
            writer.WriteInt32(vertex.BoneId[i]);
        }
        for (int i = 0; i < MAX_BONE_LENGTH; ++i)
        {
            writer.WriteFloat32(vertex.BoneWeight[i]);
        }
    }

    // indices
    for (auto &index : indices)
    {
        writer.WriteInt32(index);
    }

    writer.Close();
    std::cout << "Exported: " << fileName << std::endl;
}

OrderedMesh::OrderedMesh(const char *filepath, std::shared_ptr<SkeletonAsset> skeletonAsset = nullptr)
{
    FileReader reader(filepath);
    auto header = reader.ReadString(8);
    std::cout << header << std::endl;
    if (header != "Ora_Mesh")
    {
        throw std::runtime_error("OrderedMesh:Import file is not a mesh file");
    }

    unordered_map<int, std::string> skinBoneIndexName;

    auto bindSkeletonName = reader.ReadUntil();
    if (bindSkeletonName == "null_skeleton" || skeletonAsset == nullptr)
    {
        skeletonBinded = false;
    }
    else
    {
        if (skeletonAsset->SkeletonType != bindSkeletonName)
        {
            std::cerr << "skeleton: " << skeletonAsset->SkeletonType << " != " << bindSkeletonName << std::endl;
            throw std::runtime_error("OrderedMesh: skeleton type is not match");
        }
        this->skeletonAsset = skeletonAsset;
        skeletonBinded = true;

        auto skinBoneNameIndexSize = reader.ReadInt32();
        for (int i = 0; i < skinBoneNameIndexSize; ++i)
        {
            auto skinBoneIndex = reader.ReadInt32();
            auto skinBoneName = reader.ReadUntil();
            skinBoneIndexName[skinBoneIndex] = skinBoneName;
        }

    }
    int32_t vertexCount = reader.ReadInt32();
    int32_t indexCount = reader.ReadInt32();
    int32_t skinCount = reader.ReadInt32();

    std::cout << "vertexCount is " << vertexCount << std::endl;
    std::cout << "indexCount is " << indexCount << std::endl;
    std::cout << "skinCount is " << skinCount << std::endl;

    vertices.resize(vertexCount);
    indices.resize(indexCount);

    // vertices
    for (auto &vertex : vertices)
    {
        vertex.Position = reader.ReadVec3();
        vertex.TexCoords = reader.ReadVec2();
        vertex.Normal = reader.ReadVec3();
        vertex.RenderMask = reader.ReadUInt32();

        for (int i = 0; i < MAX_BONE_LENGTH; ++i)
        {
            vertex.BoneId[i] = reader.ReadInt32();
            if (skinBoneIndexName.size() > 0 && skeletonBinded){
                vertex.BoneId[i] = skeletonAsset->skinBoneNameIndex[skinBoneIndexName[vertex.BoneId[i]]];
            }
        }
        for (int i = 0; i < MAX_BONE_LENGTH; ++i)
        {
            vertex.BoneWeight[i] = reader.ReadFloat32();
        }
    }

    // indices
    for (auto &index : indices)
    {
        index = reader.ReadInt32();
    }

    reader.Close();

    SetupMesh();
}

void OrderedMesh::FlushInstance()
{
    if (!initialized)
    {
        std::string error = "OrderedMesh: FlushInstance: OrderedMesh not initialized";
        throw std::runtime_error(error.c_str());
    }
    instanceCount = 0;
    this->instancesToDraw.clear();
    instanceDatas.clear();
}

void OrderedMesh::AddInstacnce(MeshInstance *instance, uint32_t instanceId, uint32_t instanceMask)
{
    if (!initialized)
    {
        std::string error = "OrderedMesh: AddInstacnce: OrderedMesh not initialized";
        throw std::runtime_error(error.c_str());
    }

    //if (instanceDatas.size() > instanceCount)
    //{
    //    instanceDatas.emplace_back(InstanceData(instanceId, instanceMask));
    //}
    //else
    //{
    //    instanceDatas[instanceCount] = InstanceData(instanceId, instanceMask);
    //}
    instanceDatas.emplace_back(InstanceData(instanceId, instanceMask));
    instanceCount++;
    instancesToDraw.push_back(instance);
}

MeshInstance::MeshInstance(std::shared_ptr<OrderedMesh> mesh, glm::mat4 modelMatrix = glm::mat4(1.0f))
{
    this->mesh = mesh;
    this->modelMatrix = modelMatrix;
}

void MeshInstance::ToDraw()
{
    if (drawId == -1)
    {
        throw std::runtime_error("MeshInstance: drawId is -1");
    }
    if (mesh == nullptr)
    {
        throw std::runtime_error("MeshInstance: mesh is null");
    }

    auto ptr = this;
    mesh->AddInstacnce(ptr, drawId, drawMask);
}